/********************************************************************************

Two Wire Interface (TWI) utility functions.

Copyright (C) 2005 by Donald R. Blake
donblake at worldnet.att.net

This program is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.  See the GNU General Public License for more details.

---------------------------------------------------------------------------------

Change Activity:

    Date       Description
   ------      -------------
  02 Sep 2005  Created.
  07 Sep 2005  Include <compat/twi.h>.
  23 Oct 2005  Added twiStart, twiStop, twiReadBytes and twiWriteBytes.
               Moved include of <compat/twi.h> to twi.h.
  03 May 2007  Eliminiated use of _BV macro.

********************************************************************************/



/********************************************************************************

                                    Includes

********************************************************************************/

#include <avr/io.h>
#include <stdbool.h>
#include "twi.h"



/********************************************************************************

                               Macros and Defines

********************************************************************************/

#define WAIT_FOR_TWINT \
  for ( \
       toCounter = toValue; \
       ( toCounter != 0 ) && ( ( TWCR & ( 1 << TWINT ) ) == 0 ); \
  )

#define WAIT_FOR_STOP_GEN \
  for ( \
       toCounter = toValue; \
       ( toCounter != 0 ) && ( ( TWCR & ( 1 << TWSTO ) ) != 0 ); \
  )

#define TWI_READY ( ( TWCR & ( 1 << TWINT ) ) != 0 )

#define SCL_FREQ 100000UL
#define TWPS     0
#if TWPS == 0
#  define TWBR_VALUE ( ( ( F_CPU / SCL_FREQ ) - 16 +  1UL ) / ( 2UL *  1UL ) )
#elif TWPS == 1
#  define TWBR_VALUE ( ( ( F_CPU / SCL_FREQ ) - 16 +  4UL ) / ( 2UL *  4UL ) )
#elif TWPS == 2
#  define TWBR_VALUE ( ( ( F_CPU / SCL_FREQ ) - 16 + 16UL ) / ( 2UL * 16UL ) )
#elif TWPS == 3
#  define TWBR_VALUE ( ( ( F_CPU / SCL_FREQ ) - 16 + 64UL ) / ( 2UL * 64UL ) )
#else
#  error Invalid TWPS value
#endif

#if ( TWBR_VALUE < 10 ) || ( TWBR_VALUE > 255 )
#  error Invalid TWBR_VALUE
#endif



/********************************************************************************

                                Global Variables

********************************************************************************/

static volatile uint8_t toCounter;  // time-out counter

static uint8_t toValue;             // starting time-out counter value



/********************************************************************************

                               External Functions

********************************************************************************/



/********************************************************************************

                            twiInit - initialize TWI

********************************************************************************/

void
twiInit(
  uint8_t timeout
)
{



  // save starting time-out counter value

  toValue = timeout;



  // initialize TWI Bit Rate Prescaler

  TWSR =
       ( ( ( TWPS & ( 1 << 1 ) ) != 0 ) ? ( 1 << TWPS1 ) : 0 ) |
       ( ( ( TWPS & ( 1 << 0 ) ) != 0 ) ? ( 1 << TWPS0 ) : 0 );



  // initialize TWI Bit Rate

  TWBR = TWBR_VALUE;



} // end twiInit



/********************************************************************************

                                    twiStart

********************************************************************************/

uint8_t          // returns zero of no error; non-zero if error
twiStart(
  uint8_t sla,  // slave address (0..127)
  uint8_t rw    // read (TW_READ) or write (TW_WRITE)
)
{



  // send START condition

  TWCR = ( 1 << TWINT ) | ( 1 << TWSTA ) | ( 1 << TWEN );



  // wait for start condition transmitted

  WAIT_FOR_TWINT;



  // clear TWI START condition bit

  TWCR = ( 1 << TWEN );



  // check for time-out and check status code

  if ( !TWI_READY ) return 1;

  if ( TW_STATUS != TW_START ) return 2;



  // load SLA+R or SLA+W

  TWDR = ( sla << 1 ) | ( rw & 0x01 );



  // transmit SLA

  TWCR = ( 1 << TWINT ) | ( 1 << TWEN );



  // wait for SLA transmitted

  WAIT_FOR_TWINT;



  // check for time-out and check status code

  if ( !TWI_READY ) return 3;

  if ( rw == TW_READ )
  {

    // receive mode

    if ( TW_STATUS != TW_MR_SLA_ACK )
    {
      if ( TW_STATUS == TW_MR_SLA_NACK ) twiStop( );
      return 4;
    } // end if

  }
  else
  {

    // transmit mode

    if ( TW_STATUS != TW_MT_SLA_ACK )
    {
      if ( TW_STATUS == TW_MT_SLA_NACK ) twiStop( );
      return 5;
    } // end if

  } // end if



  return 0;



} // end twiStart



/********************************************************************************

                                    twiStop

********************************************************************************/

uint8_t   // returns zero of no error; non-zero if error
twiStop(
  void
)
{



  // generate a STOP condition

  TWCR = ( 1 << TWINT ) | ( 1 << TWSTO ) | ( 1 << TWEN );



  // wait for STOP generated

  WAIT_FOR_STOP_GEN;



  // check for timeout

  if ( ( TWCR & ( 1 << TWSTO ) ) != 0 ) return 6;



  return 0;



} // end twiStop



/********************************************************************************

                                    twiWrite

********************************************************************************/

uint8_t
twiWrite(
  uint8_t  sla,
  uint8_t* data,
  uint8_t  count
)
{



  uint8_t rc;



  // send SLA+W

  rc = twiStart( sla, TW_WRITE );

  if ( rc != 0 ) return rc;



  // send data byte(s)

  rc = twiWriteBytes( data, count );

  if ( rc != 0 ) return rc;



  // generate STOP

  rc = twiStop( );

  if ( rc != 0 ) return rc;



  return 0;



} // end twiWrite



/********************************************************************************

                                    twiRead

********************************************************************************/

uint8_t
twiRead(
  uint8_t  sla,
  uint8_t* data,
  uint8_t  count
)
{



  uint8_t rc;



  // send SLA+R

  rc = twiStart( sla, TW_READ );

  if ( rc != 0 ) return rc;



  // read data byte(s)

  rc = twiReadBytes( data, count, true );

  if ( rc != 0 ) return rc;



  // generate STOP

  rc = twiStop( );

  if ( rc != 0 ) return rc;



  return 0;



} // end read data



/*******************************************************************************

                                 twiWriteBytes

*******************************************************************************/

uint8_t
twiWriteBytes(
  uint8_t* data,
  uint8_t  count
)
{



  // send data byte(s)

  do
  {



    // send next data byte

    TWDR = *data++;

    TWCR = ( 1 << TWINT ) | ( 1 << TWEN );



    // wait for data byte transmitted

    WAIT_FOR_TWINT;



    // check for timeout and check status code

    if ( !TWI_READY ) return 7;

    if ( TW_STATUS != TW_MT_DATA_ACK )
    {
      if ( TW_STATUS == TW_MT_DATA_NACK ) twiStop( );
      return 8;
    } // end if



  } while ( --count != 0 );



  return 0;



} // end twiWriteBytes



/*******************************************************************************

                                  twiReadBytes

*******************************************************************************/

uint8_t
twiReadBytes(
  uint8_t* data,
  uint8_t  count,
  bool     nack
)
{



  // read data byte(s)

  do
  {



    // set up to receive next data byte - send NACK on last byte, if requested

    TWCR =
         ( 1 << TWINT ) |
         ( 1 << TWEN) |
         ( ( ( count == 1 ) && nack ) ? 0 : ( 1 << TWEA ) );



    // wait for data byte

    WAIT_FOR_TWINT;



    // check for timeout and check status

    if ( !TWI_READY ) return 9;

    if ( TW_STATUS !=
         ( ( ( count == 1 ) && nack ) ? TW_MR_DATA_NACK : TW_MR_DATA_ACK ) )
    {
      twiStop( );
      return 10;
    } // end if



    // read data byte

    *(data++) = TWDR;



  } while ( --count != 0 );



  return 0;



} // end twiReadBytes



/********************************************************************************

                     twiDecTo - decrement time-out counter

********************************************************************************/

void
twiDecTo(
  void
)
{

  if ( toCounter != 0 ) toCounter--;

} // end twiDecTo
